#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <libgen.h>
#include <limits.h>
#include <getopt.h>
#include <unistd.h>
#include <dirent.h>

#include "../util.h"
#include "../getopt_helper.h"

enum {
	IGNORE_FAIL = GETOPT_LONG_ENUM_INIT,
};

static struct option const LONG_OPTIONS[] = {
	{ "ignore-fail-on-non-empty", no_argument, NULL, IGNORE_FAIL },

	{ "path", no_argument, NULL, 'p' }, /* Deprecated.  */
	{ "parents", no_argument, NULL, 'p' },
	{ "verbose", no_argument, NULL, 'v' },
	GETOPT_END_DECL
};

static const char usage_str[] =
    "Usage: %s [OPTION]... DIRECTORY...\n"
    "Remove the DIRECTORY(ies), if they are empty.\n"
    "\n"
    "      --ignore-fail-on-non-empty\n"
    "                 ignore each failure that is solely because a directory\n"
    "                   is non-empty\n"
    "  -p, --parents  remove DIRECTORY and its ancestors; e.g., 'rmdir -p "
    "a/b/c' is\n"
    "                   similar to 'rmdir a/b/c a/b a'\n"
    "  -v, --verbose  output a diagnostic for every directory processed\n"
    "      --help     display this help and exit\n"
    "      --version  output version information and exit";

static enum flag ignorable_errno(int e, const char* dir);

int
main(int argc, char* argv[])
{
	int ch, status = 0;

	enum flag verbose = NO, remove_parents = NO, ignore_fail = NO;

	while ((ch = getopt_long(argc, argv, "pv", LONG_OPTIONS, NULL))
	       != -1) {
		switch (ch) {
		case 'p':
			remove_parents = YES;
			break;

		case 'v':
			verbose = YES;
			break;

		case IGNORE_FAIL:
			ignore_fail = YES;
			break;

			GETOPT_END_CASE
		}
	}

	if (argc <= optind) errx(!0, "missing operand");

	for (argv += optind; *argv; ++argv) {
		int   rmdir_errno;
		char* dir = *argv;

		do {
			if (verbose)
				printf("%s: removing directory '%s'\n",
				       __progname,
				       dir);

			if (!rmdir(dir)) goto rmdir_ok;

			status	    = !0;
			rmdir_errno = errno;

			if (ignore_fail && ignorable_errno(rmdir_errno, dir))
				goto end_for;

			errno = rmdir_errno;
			warn("failed to remove '%s'", dir);

		rmdir_ok:
			if (!remove_parents) break;
		} while (((dir = dirname(dir))
			  && (!STREQ(".", dir) && !STREQ("/", dir))));

	end_for:
		NULL;
	}

	return status;
}

static inline enum flag
may_be_non_emty(int e)
{
	switch (e) {
	case EACCES: /* fallthrough */
	case EPERM:  /* fallthrough */
	case EROFS:  /* fallthrough */
	case EBUSY:
		return YES;

	default:
		return NO;
	}
}

static inline enum flag
is_emty_dir(const char* dir)
{
#define IS_DOTS(D)                                                            \
	((D)->d_name[0] == '.'                                                \
	 && (!(D)->d_name[1] || ((D)->d_name[1] == '.' && !(D)->d_name[2])))

	DIR* dir_ptr;

	int saved_errno;

	struct dirent* ent;

	if (!(dir_ptr = opendir(dir))) return NO;

	errno = 0;

	while (!!(ent = readdir(dir_ptr)))
		if (!IS_DOTS(ent)) break;

	saved_errno = errno;
	closedir(dir_ptr);
	errno = saved_errno;

	if (!!ent) return NO;

	return !saved_errno;
}

static enum flag
ignorable_errno(int e, const char* dir)
{
#define NON_EMPTY(E) ((E) == ENOTEMPTY || (E) == EEXIST)

	return NON_EMPTY(e)
	    || (may_be_non_emty(e) && (is_emty_dir(dir) && !errno));
}
